#include "codegen/mem_direct.hpp"
#define MAX_VAR_PERFILE 5000
namespace picker { namespace codegen {

    static const std::string yaml_varible_info =
        "    cpp_variableInfo {{var_hash}} = {\"{{var_name}}\", \"{{var_type}}\", {{var_width}}, {{var_array_size}}, (uint64_t)&(base.{{var_raw_name}}) - (uint64_t)&base };\n"
        "    {{var_hash}}.write_yaml();\n";

    std::string replace_dot(const std::string &varName)
    {
        // __DOT__ -> .  , fast mode
        std::string out;
        out.reserve(varName.size());
        for (size_t i = 0; i < varName.size(); i++) {
            if (i + 6 < varName.size() && varName[i] == '_' && varName[i + 1] == '_' && varName[i + 2] == 'D'
                && varName[i + 3] == 'O' && varName[i + 4] == 'T' && varName[i + 5] == '_' && varName[i + 6] == '_') {
                out.push_back('.');
                i += 6;
            } else {
                out.push_back(varName[i]);
            }
        }
        return out;
    }

    void generate_offset(const std::vector<cpp_variableInfo> &varibles, std::vector<std::string> &offset_array,
                         std::string &offset_funcs, std::string &offset_funcs_define)
    {
        inja::Environment env;
        nlohmann::json data;
        int var_count = 0, file_count = 0;
        offset_array.push_back("");

        for (auto &var : varibles) {
            // calc hash from name
            data["var_hash"]       = "hash_" + std::to_string(std::hash<std::string>{}(var.name));
            data["var_raw_name"]   = var.name;
            data["var_name"]       = replace_dot(var.name);
            data["var_type"]       = var.type;
            data["var_width"]      = var.width;
            data["var_array_size"] = var.array_size;
            offset_array[file_count] += env.render(yaml_varible_info, data);
            if (++var_count >= MAX_VAR_PERFILE) {
                var_count = 0;
                file_count++;
                offset_array.push_back("");
            }
        }
        for (int i = 0; i < offset_array.size(); i++) {
            offset_funcs += "\t\trender_varible_info_" + std::to_string(i) + "(base);\n";
            offset_funcs_define += "void render_varible_info_" + std::to_string(i) + "(BaseType &base);\n";
        }
    }
    

    void split_offset(const std::vector<std::string> &offset_array, std::string &src_dir, std::string &dst_dir)
    {
        nlohmann::json data;
        
        for (size_t i = 0; i < offset_array.size(); i++) {
            // copy gen_addr_sub_.cpp to gen_addr_sub_i.cpp
            std::string src_file = src_dir + "/gen_addr_sub_.cpp";
            std::string dst_file = dst_dir + "/gen_addr_sub_" + std::to_string(i) + ".cpp";
            std::ifstream src(src_file);
            std::ofstream dst(dst_file);
            // sed -i "s/{{__sub_i__}}/i/g" gen_addr_sub_i.cpp
            std::string line;
            while (std::getline(src, line))
                dst << std::regex_replace(line, std::regex("\\{\\{__sub_i__\\}\\}"), std::to_string(i)) << std::endl;
            src.close();
            dst.close();
        }
    }

    void render_md_addr_generator(const std::vector<cpp_variableInfo> &varibles, picker::export_opts &opts)
    {
        if (varibles.size() == 0) { return; }
        inja::Environment env;
        nlohmann::json data;
        std::vector<std::string> offset_array;
        std::string offset_funcs, offset_funcs_define;              // begin with "render_varible_info_0(base);\n"
        std::string src_dir = opts.source_dir; // "mem_direct" folder has been added in the upper level
        std::string dst_dir = opts.target_dir;
        std::string dst_dir_tmp = std::filesystem::path(dst_dir).parent_path().string() + "/mem_direct_tmp";
        // copy files
        std::filesystem::create_directories(dst_dir_tmp);
        std::filesystem::copy(src_dir + "/gen_addr.hpp", dst_dir_tmp + "/gen_addr.hpp", std::filesystem::copy_options::overwrite_existing);
        std::filesystem::copy(src_dir + "/gen_addr.cpp", dst_dir_tmp + "/gen_addr.cpp", std::filesystem::copy_options::overwrite_existing);
        std::filesystem::copy(src_dir + "/Makefile", dst_dir_tmp + "/Makefile", std::filesystem::copy_options::overwrite_existing);
        generate_offset(varibles, offset_array, offset_funcs, offset_funcs_define);
        split_offset(offset_array, src_dir, dst_dir_tmp);
        data["__TOP_MODULE_NAME__"] = opts.source_module_name_list[0];
        for (int i = 0; i < offset_array.size(); i++)
            data["__yaml_varible_info_" + std::to_string(i) + "__"] = offset_array[i];
        data["__render_varible_info_sub__"] = "  generated by codegen\n" + offset_funcs;
        data["__render_varible_info_sub_define__"] = offset_funcs_define;
        std::set<std::string> type_set;
        for (const auto &var : varibles) {
            type_set.insert(var.type);
        }
        auto cpp_type_set = nlohmann::json::array();
        for (const auto &type : type_set) {
            cpp_type_set.push_back(type);
        }
        data["__yaml_varible_type_set__"] = cpp_type_set;
        data["__SIMULATOR__"] = opts.sim;
        recursive_render(dst_dir_tmp, dst_dir, data, env);
    }

}}; // namespace picker::codegen